(* Written by Zach Salzbank, Erica Sponsler and Nate Weiss *)

type op = Add | Sub | Mult | Div | Equal | Neq | Less | Leq | Greater | Geq | BoolAnd | BoolOr

type constant =
    Integer of int
  | Character of char 
  | Boolean of bool
  | Null

type obj_type = 
    IntType
  | CharType
  | BooleanType
  | VoidType
  | NodeType of obj_type
  | NullType

type l_value =
    Id of string
  | Unop of l_value * unary_op

and unary_op = Child of expr | ValueOf

and expr =
    Literal of constant
  | LValue of l_value
  | Node of expr
  | Binop of expr * op * expr
  | Assign of l_value * expr
  | Call of string * expr list
  | Neg of expr
  | Bang of expr
  | Noexpr

type v_decl = {
    vname: string;
    vtype: obj_type;
    vdefault: expr option;
}

type stmt =
    Block of v_decl list * stmt list
  | Expr of expr
  | Return of expr
  | If of expr * stmt * stmt
  | While of expr * stmt

type func_decl = {
    ftype: obj_type;
    fname : string;
    formals : v_decl list;
    locals : v_decl list;
    body : stmt list;
  }

type program = v_decl list * func_decl list

let string_of_char = function
    '\'' -> "\\'"
  | '\\' -> "\\\\"
  | _ as c   -> String.make 1 c
      
let string_of_op = function
    Add -> "+"
  | Sub -> "-"
  | Mult -> "*"
  | Div -> "/"
  | Equal -> "=="
  | Neq -> "!="
  | Less -> "<"
  | Leq -> "<=" 
  | Greater -> ">" 
  | Geq -> ">="
  | BoolAnd -> "&&"
  | BoolOr -> "||"

let rec string_of_const = function
    Integer(l) -> string_of_int l
  | Character(c) -> "'" ^ string_of_char c ^ "'"
  | Boolean(b) -> if b then "true" else "false"
  | Null -> "null"

let rec string_of_unop = function
    ValueOf -> ".value"
  | Child(e) -> "[" ^ string_of_expr e ^ "]"

and string_of_lval = function
    Id(s) -> s
  | Unop (l, uo) -> string_of_lval l ^ string_of_unop uo

and string_of_expr = function
    Literal(c) -> (match c with
        Integer(l) -> string_of_int l
      | Character(c) -> "'" ^ string_of_char c ^ "'"
      | Boolean(b) -> if b then "true" else "false"
      | Null -> "null"
    )
  | LValue(l) -> string_of_lval l
  | Node(e) -> "<" ^ string_of_expr e ^ ">"
  | Binop(e1, o, e2) ->
      string_of_expr e1 ^ " " ^ string_of_op o ^ " " ^ string_of_expr e2
  | Assign(l, e) -> string_of_lval l ^ " = " ^ string_of_expr e
  | Call(f, el) ->
      f ^ "(" ^ String.concat ", " (List.map string_of_expr el) ^ ")"
  | Neg(e) -> "-" ^ string_of_expr e
  | Bang(e) -> "!" ^ string_of_expr e
  | Noexpr -> ""

let rec string_of_obj_type t = match t with
    IntType -> "int"
  | CharType -> "char"
  | BooleanType -> "boolean"
  | VoidType -> "void"
  | NodeType(s) -> "Node<" ^ string_of_obj_type s ^ ">"
  | NullType -> "null"

let string_of_vdecl id = match id.vdefault with
    None -> string_of_obj_type id.vtype ^ " " ^ id.vname ^ ";\n"
  | Some(d) -> string_of_obj_type id.vtype ^ " " ^ id.vname ^ " = " ^ string_of_expr d ^ ";\n"

let rec string_of_stmt = function
    Block(vars, stmts) ->
      "{\n" ^ String.concat "" (List.map string_of_vdecl vars) ^ "\n" ^
       String.concat "" (List.map string_of_stmt stmts) ^ "}\n"
  | Expr(expr) -> string_of_expr expr ^ ";\n";
  | Return(expr) -> "return " ^ string_of_expr expr ^ ";\n";
  | If(e, s, Block([], [])) -> "if (" ^ string_of_expr e ^ ")\n" ^ string_of_stmt s
  | If(e, s1, s2) ->  "if (" ^ string_of_expr e ^ ")\n" ^
      string_of_stmt s1 ^ "else\n" ^ string_of_stmt s2
  | While(e, s) -> "while (" ^ string_of_expr e ^ ") " ^ string_of_stmt s

let string_of_fdecl fdecl =
  string_of_obj_type fdecl.ftype ^ " " ^
  fdecl.fname ^ "(" ^ String.concat ", " (List.map string_of_vdecl
  fdecl.formals) ^ ")\n{\n" ^
  String.concat "" (List.map string_of_vdecl fdecl.locals) ^
  String.concat "" (List.map string_of_stmt fdecl.body) ^
  "}\n"

let string_of_program (vars, funcs) =
  String.concat "" (List.map string_of_vdecl vars) ^ "\n" ^
  String.concat "\n" (List.map string_of_fdecl funcs)
